package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

const (
	BaseWorkUrl    = "/usershen/user_work"         //用户工作目录
	BaseInterimUrl = "/usershen/user_work_interim" //用户克隆过渡目录
	BaseBackupUrl  = "/usershen/user_work_backup"  //用户代码备份目录
)

//	deleteDirectory
//
// 描述信息：删除指定的目录及其所有内容, 如果目录存在，则删除；如果目录不存在，则不做任何操作。
//
// Param：
//
//	dirPath ：指定文件目录
//
// Return：
//
//	bool ：表示操作是否成功
//	error ：如果操作失败，返回具体的错误信息；否则返回 nil。
func deleteDirectory(dirPath string) (bool, error) {
	// 检查目录是否存在
	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
		// 目录不存在，无需删除
		return true, nil
	} else if err != nil {
		// 其他错误
		return false, fmt.Errorf("检查目录时出错: %v", err)
	}

	// 目录存在，删除目录及其所有内容
	if err := os.RemoveAll(dirPath); err != nil {
		return false, fmt.Errorf("删除目录失败: %v", err)
	}

	// 删除成功
	return true, nil
}

//	cloneRepository
//
// 描述信息： 在过渡目录中执行 Git 克隆操作，并将克隆的内容复制到工作目录 同时，将工作目录的内容备份到备份目录
//
// Param：
//
//	projectURL ：项目的 http url地址
//	userID ：用户id
//	teamID ：团队id
//	path ：git克隆时会自动生成的目录名称
//
// Return：
//
//	error ：错误信息
func cloneRepository(projectURL string, userID string, teamID string, path string) error {
	// 构建目录路径
	workDir := filepath.Join(BaseWorkUrl, userID, teamID)
	interimDir := filepath.Join(BaseInterimUrl, userID, teamID)
	backupDir := filepath.Join(BaseBackupUrl, userID, teamID)
	interimPathDir := filepath.Join(interimDir, path)
	workPathDir := filepath.Join(workDir, path)
	backupPathDir := filepath.Join(backupDir, path)

	// 确保过渡目录存在
	if err := ensureDirectoryExists(interimDir); err != nil {
		return fmt.Errorf("确保过渡目录存在失败: %v", err)
	}

	// 检查过渡目录是否存在 path 文件夹，如果存在，删除
	if _, err := deleteDirectory(interimPathDir); err != nil {
		return fmt.Errorf("删除过渡目录中的 path 文件夹失败: %v", err)
	}

	// 构建 Git 克隆命令
	cloneCmd := fmt.Sprintf("git clone %s", projectURL)

	// 在过渡目录中执行 Git 克隆命令
	cmd := exec.Command("bash", "-c", cloneCmd)
	cmd.Dir = interimDir

	fmt.Println("正在执行 Git 克隆操作...")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("Git 克隆失败: %s, 错误: %v", output, err)
	}

	fmt.Println("Git 克隆成功！")

	// 确保备份目录存在
	if err := ensureDirectoryExists(backupDir); err != nil {
		return fmt.Errorf("确保备份目录存在失败: %v", err)
	}

	// 检查备份目录是否存在 path 文件夹，如果存在，删除
	if _, err := deleteDirectory(backupPathDir); err != nil {
		return fmt.Errorf("删除备份目录中的 path 文件夹失败: %v", err)
	}

	// 如果备份目录中不存在 path 文件夹，将工作目录中的 path 文件夹复制到备份目录
	if _, err := os.Stat(workPathDir); err == nil {
		if err := copyDirectory(workPathDir, backupPathDir); err != nil {
			return fmt.Errorf("将工作目录中的 path 文件夹复制到备份目录失败: %v", err)
		}
		fmt.Println("工作目录中的 path 文件夹已成功备份到备份目录。")
	} else if !os.IsNotExist(err) {
		return fmt.Errorf("检查工作目录中的 path 文件夹时出错: %v", err)
	}

	// 删除工作目录中的 path 文件夹
	if _, err := deleteDirectory(workPathDir); err != nil {
		return fmt.Errorf("删除工作目录中的 path 文件夹失败: %v", err)
	}

	// 将过渡目录中的 path 文件夹复制到工作目录
	if err := copyDirectory(interimPathDir, workPathDir); err != nil {
		return fmt.Errorf("将过渡目录中的 path 文件夹复制到工作目录失败: %v", err)
	}

	fmt.Println("克隆的内容已成功复制到工作目录。")
	return nil
}

// ensureDirectoryExists 确保指定的目录存在，如果不存在则创建
func ensureDirectoryExists(dirPath string) error {
	// 检查目录是否存在
	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
		// 目录不存在，创建目录及其所有父目录
		if err := os.MkdirAll(dirPath, 0755); err != nil {
			return fmt.Errorf("创建目录失败: %v", err)
		}
	} else if err != nil {
		// 其他错误
		return fmt.Errorf("检查目录时出错: %v", err)
	}
	// 目录已存在，无需操作
	return nil
}

// copyDirectory 将源目录的内容复制到目标目录
func copyDirectory(src, dst string) error {
	// 创建目标目录
	if err := os.MkdirAll(dst, 0755); err != nil {
		return fmt.Errorf("创建目标目录失败: %v", err)
	}

	// 遍历源目录
	srcInfo, err := os.Stat(src)
	if err != nil {
		return fmt.Errorf("读取源目录信息失败: %v", err)
	}

	if !srcInfo.IsDir() {
		return fmt.Errorf("源路径不是一个目录")
	}

	files, err := os.ReadDir(src)
	if err != nil {
		return fmt.Errorf("读取源目录内容失败: %v", err)
	}

	for _, file := range files {
		srcPath := filepath.Join(src, file.Name())
		dstPath := filepath.Join(dst, file.Name())

		if file.IsDir() {
			// 递归复制子目录
			if err := copyDirectory(srcPath, dstPath); err != nil {
				return err
			}
		} else {
			// 复制文件
			if err := copyFile(srcPath, dstPath); err != nil {
				return err
			}
		}
	}

	return nil
}

// copyFile 将单个文件从源路径复制到目标路径
func copyFile(src, dst string) error {
	srcFile, err := os.Open(src)
	if err != nil {
		return fmt.Errorf("打开源文件失败: %v", err)
	}
	defer srcFile.Close()

	dstFile, err := os.Create(dst)
	if err != nil {
		return fmt.Errorf("创建目标文件失败: %v", err)
	}
	defer dstFile.Close()

	_, err = io.Copy(dstFile, srcFile)
	if err != nil {
		return fmt.Errorf("复制文件内容失败: %v", err)
	}

	return nil
}

//	buildDockerImage
//
// 描述信息： 构建 Docker 镜像
//
// Param：
//
//	filePath ：包含项目代码和 Dockerfile 的文件夹路径
//	userId: 用户 ID
//	teamId: 团队 ID
//	projectId: 项目 ID
//
// Return：
//
//	bool: 构建是否成功
//	string: 构建的镜像名称
//	error: 错误信息
func buildDockerImage(filePath string, userId string, teamId string, projectId string) (bool, string, error) {
	// 构建镜像名称
	imageName := fmt.Sprintf("%s_%s_%s", userId, teamId, projectId)

	// 构建 Docker 命令
	cmd := exec.Command("docker", "build", "-t", imageName, filePath)

	// 捕获命令的输出
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &out

	// 执行命令
	err := cmd.Run()

	// 检查命令是否成功
	if err != nil {
		// 如果失败，返回 false 和错误信息
		return false, "", fmt.Errorf("failed to build image: %v\nOutput: %s", err, out.String())
	}

	// 如果成功，返回 true 和镜像名称
	return true, imageName, nil
}

/*修改了*/
//	deleteContainer
//
// 描述信息： 删除指定名称的 Docker 容器
//
// Param：
//
//	containerName ：容器名称
//
// Return：
//
//	bool ：操作是否成功
//	error ：错误信息
func deleteContainer(containerName string) (bool, error) {
	// 检查容器是否存在
	cmd := exec.Command("docker", "inspect", containerName)
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &out

	err := cmd.Run()
	if err != nil {
		// 如果容器不存在，docker inspect 会返回错误
		if strings.Contains(out.String(), "No such object") {
			// 容器不存在，直接返回成功
			return true, nil
		}
		// 其他错误
		return false, fmt.Errorf("failed to inspect container: %v\nOutput: %s", err, out.String())
	}

	// 容器存在，删除容器
	cmd = exec.Command("docker", "rm", "-f", containerName)
	out.Reset()
	cmd.Stdout = &out
	cmd.Stderr = &out

	err = cmd.Run()
	if err != nil {
		return false, fmt.Errorf("failed to remove container: %v\nOutput: %s", err, out.String())
	}

	// 删除成功
	return true, nil
}

//	runContainer
//
// 描述信息： 根据镜像创建并运行一个容器，并将容器内的 8080 端口映射到主机的指定端口
//
// Param：
//
//	containerName ：容器名称
//	imageName ：镜像名称
//	hostPort ：主机端口号
//
// Return：
//
//	bool ：操作是否成功
//	string ：主机端口号
//	error ：错误信息
func runContainer(containerName string, imageName string, hostPort string) (bool, string, error) {
	// 构建 Docker 命令
	cmd := exec.Command("docker", "run", "-d", "--name", containerName, "-p", hostPort+":8080", imageName)

	// 捕获命令的输出
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = &out

	// 执行命令
	err := cmd.Run()
	if err != nil {
		// 如果命令执行失败，返回 false 和错误信息
		return false, "", fmt.Errorf("failed to run container: %v\nOutput: %s", err, out.String())
	}

	// 如果命令执行成功，返回 true 和主机端口号
	return true, hostPort, nil
}

// deployFromGit
//
// 描述信息：从指定的 Git 地址拉取代码，并根据用户工作目录中的内容使用 Docker 部署，返回主机映射的端口。
//
//	如果部署失败，自动回滚到备份目录中的内容。
//
// Param：
//
//	projectURL ：项目的 Git URL 地址
//	userID ：用户 ID
//	teamID ：团队 ID
//	path ：Git 克隆时会自动生成的目录名称
//	hostPort ：主机端口号
//
// Return：
//
//	bool ：操作是否成功
//	string ：主机映射的端口
//	error ：错误信息
func deployFromGit(projectURL string, userID string, teamID string, path string, hostPort string) (bool, string, error) {
	// 构建备份目录路径
	backupDir := filepath.Join(BaseBackupUrl, userID, teamID, path)

	// Step 1: 克隆代码到用户工作目录
	if err := cloneRepository(projectURL, userID, teamID, path); err != nil {
		fmt.Println("克隆代码失败开始回滚")
		// 如果克隆失败，尝试从备份目录恢复
		if err := rollbackFromBackup(backupDir, userID, teamID, path, hostPort); err != nil {
			return false, "", fmt.Errorf("克隆代码失败且回滚失败: %v", err)
		}
		return true, hostPort, nil
	}

	// Step 2: 构建 Docker 镜像
	workDir := filepath.Join(BaseWorkUrl, userID, teamID, path)
	success, imageName, err := buildDockerImage(workDir, userID, teamID, path)
	if !success {
		// 如果构建失败，尝试从备份目录恢复
		fmt.Println("构建镜像失败开始回滚")
		if err := rollbackFromBackup(backupDir, userID, teamID, path, hostPort); err != nil {
			return false, "", fmt.Errorf("构建 Docker 镜像失败且回滚失败: %v", err)
		}
		return true, hostPort, nil
	}

	// Step 3: 删除旧的容器（如果存在）
	containerName := fmt.Sprintf("container_%s_%s_%s", userID, teamID, path)
	_, err = deleteContainer(containerName)
	if err != nil {
		// 如果删除容器失败，尝试从备份目录恢复
		fmt.Println("删除容器失败开始回滚")
		if err := rollbackFromBackup(backupDir, userID, teamID, path, hostPort); err != nil {
			return false, "", fmt.Errorf("删除旧容器失败且回滚失败: %v", err)
		}
		return true, hostPort, nil
	}

	// Step 4: 运行新的容器
	success, port, err := runContainer(containerName, imageName, hostPort)
	if !success {
		// 如果运行容器失败，尝试从备份目录恢复
		fmt.Println("运行容器失败开始回滚")
		if err := rollbackFromBackup(backupDir, userID, teamID, path, hostPort); err != nil {
			return false, "", fmt.Errorf("运行容器失败且回滚失败: %v", err)
		}
		return true, hostPort, nil
	}

	// 返回成功信息和主机映射的端口
	return true, port, nil
}

// rollbackFromBackup
//
// 描述信息：从备份目录中恢复代码，并重新构建和运行容器。
//
// Param：
//
//	backupDir ：备份目录路径
//	userID ：用户 ID
//	teamID ：团队 ID
//	path ：目录名称
//	hostPort ：主机端口号
//
// Return：
//
//	error ：错误信息
func rollbackFromBackup(backupDir string, userID string, teamID string, path string, hostPort string) error {
	// Step 1: 确保备份目录存在
	if _, err := os.Stat(backupDir); os.IsNotExist(err) {
		return fmt.Errorf("备份目录不存在: %s", backupDir)
	}

	// Step 2: 清空工作目录
	workDir := filepath.Join(BaseWorkUrl, userID, teamID, path)
	if _, err := deleteDirectory(workDir); err != nil {
		return fmt.Errorf("清空工作目录失败: %v", err)
	}

	// Step 3: 将备份目录中的内容复制到工作目录
	if err := copyDirectory(backupDir, workDir); err != nil {
		return fmt.Errorf("从备份目录复制内容到工作目录失败: %v", err)
	}

	// Step 4: 构建新的 Docker 镜像
	success, imageName, err := buildDockerImage(workDir, userID, teamID, path)
	if !success {
		return fmt.Errorf("从备份目录恢复后构建 Docker 镜像失败: %v", err)
	}

	// Step 5: 删除旧的容器（如果存在）
	containerName := fmt.Sprintf("container_%s_%s_%s", userID, teamID, path)
	_, err = deleteContainer(containerName)
	if err != nil {
		return fmt.Errorf("从备份目录恢复后删除旧容器失败: %v", err)
	}

	// Step 6: 运行新的容器
	success, _, err = runContainer(containerName, imageName, hostPort)
	if !success {
		return fmt.Errorf("从备份目录恢复后运行容器失败: %v", err)
	}

	return nil
}

// GetPublicIP 获取服务器的公网 IP 地址
func GetPublicIP() (string, error) {
	// 使用 httpbin.org 提供的公网 IP 查询服务
	url := "https://httpbin.org/ip"

	// 发起 HTTP GET 请求
	resp, err := http.Get(url)
	if err != nil {
		return "", fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	// 检查 HTTP 响应状态码
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("failed to get public IP, status code: %d", resp.StatusCode)
	}

	// 读取响应内容
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("failed to read response body: %w", err)
	}

	// 解析 JSON 响应
	var result map[string]interface{}
	if err := json.Unmarshal(body, &result); err != nil {
		return "", fmt.Errorf("failed to parse JSON response: %w", err)
	}

	// 提取 IP 地址
	ip, ok := result["origin"].(string)
	if !ok {
		return "", fmt.Errorf("failed to extract IP address from response")
	}

	return ip, nil
}

// ConcatenateWithUnderscores 使用 strings.Builder 高效连接字符串
func ConcatenateWithUnderscores(a, b, c, d string) string {
	var sb strings.Builder
	sb.WriteString(a)
	sb.WriteRune('_')
	sb.WriteString(b)
	sb.WriteRune('_')
	sb.WriteString(c)
	sb.WriteRune('_')
	sb.WriteString(d)
	return sb.String()
}

func main() {
	// 测试函数
	result := ConcatenateWithUnderscores("container", "1", "1", "test-webhook")
	fmt.Println(result) // 输出: a_b_c_d
}
